gtk/main: Keep implicit grab until all buttons are released
authorCarlos Garnacho <carlosg@gnome.org>
Tue, 8 Dec 2020 22:13:50 +0000 (23:13 +0100)
committerCarlos Garnacho <carlosg@gnome.org>
Tue, 8 Dec 2020 22:27:03 +0000 (23:27 +0100)
Currently, the implicit grab is broken on the first button release,
in the case of pressing multiple buttons simultaneously. This means
that we emit crossing events early, and the next button releases
are sent to the pointer focus widget instead.

Consider the implicit grab effective until all buttons are released,
and only unset the pointer implicit grab (and emit crossing events)
after there are no further buttons pressed. We do this by checking
event modifiers, given button release events do contain the modifiers
in effect at the time the event was generated, we have to look for
exactly one active button modifier.

Fixes weird pointer states after pressing multiple buttons on a
widget.

Fixes: https://gitlab.gnome.org/GNOME/gtk/-/issues/3426
gtk/gtkmain.c

index b9d12e19aa83ab714811215cc5097ab17fcb75ef..13aa3dd10e5ce1e9b06f74ddfacb39bf4dc2d120 100644 (file)
@@ -1375,6 +1375,7 @@ handle_pointing_event (GdkEvent *event)
   GtkWidget *native;
   GdkEventType type;
   gboolean has_implicit;
+  GdkModifierType modifiers;
 
   event_widget = gtk_get_event_widget (event);
   device = gdk_event_get_device (event);
@@ -1480,14 +1481,17 @@ handle_pointing_event (GdkEvent *event)
         gtk_window_lookup_pointer_focus_implicit_grab (toplevel,
                                                        device,
                                                        sequence) != NULL;
+      modifiers = gdk_event_get_modifier_state (event);
 
-      gtk_window_set_pointer_focus_grab (toplevel, device, sequence,
-                                         type == GDK_BUTTON_PRESS ?  target : NULL);
-
-      if (type == GDK_BUTTON_RELEASE)
+      if (type == GDK_BUTTON_RELEASE &&
+          __builtin_popcount (modifiers &
+                              (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK |
+                               GDK_BUTTON4_MASK | GDK_BUTTON5_MASK)) == 1)
         {
           GtkWidget *new_target = gtk_widget_pick (native, x, y, GTK_PICK_DEFAULT);
 
+          gtk_window_set_pointer_focus_grab (toplevel, device, sequence, NULL);
+
           if (new_target == NULL)
             new_target = GTK_WIDGET (toplevel);
 
@@ -1496,6 +1500,11 @@ handle_pointing_event (GdkEvent *event)
           gtk_window_maybe_update_cursor (toplevel, NULL, device);
           update_pointer_focus_state (toplevel, event, new_target);
         }
+      else if (type == GDK_BUTTON_PRESS)
+        {
+          gtk_window_set_pointer_focus_grab (toplevel, device,
+                                             sequence, target);
+        }
 
       if (type == GDK_BUTTON_PRESS)
         set_widget_active_state (target, FALSE);